﻿/*
  Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL/
  
  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the
  License.
  
  The Original Code is [maashaack framework].
  
  The Initial Developers of the Original Code are
  Zwetan Kjukov <zwetan@gmail.com> and Marc Alcaraz <ekameleon@gmail.com>.
  Portions created by the Initial Developers are Copyright (C) 2004-2014
  the Initial Developers. All Rights Reserved.
  
  Contributor(s):
  
  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the LGPL or the GPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.
*/
package molecule.render.starling.textures
{
    import starling.text.BitmapFont;
    import starling.textures.Texture;
    import starling.textures.TextureAtlas;
    
    import flash.display.BitmapData;
    import flash.display.DisplayObject;
    import flash.display.MovieClip;
    import flash.display.Sprite;
    import flash.display.getRealBounds;
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    import flash.geom.Rectangle;
    import flash.text.AntiAliasType;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.text.isEmbedded;
    import flash.utils.getQualifiedClassName;
    
    /**
     * Generates dynamic textureAtlas based on MovieClips or TextFields.
     */
    public class DynamicTextureAtlas
    {
        /**
         * Creates a DynamicTextureAtlas instance.
         */
        public function DynamicTextureAtlas()
        {
            //
        }
        
        /**
         * This method takes a vector of DisplayObject class and converts it into a Texture Atlas.
         * @param assets The DisplayObject classes you wish to convert into a TextureAtlas. Must contain classes whose instances are of type DisplayObject that will be rasterized and become the subtextures of your Atlas.
         * @param scaleFactor The scaling factor to apply to every object. Default value is 1 (no scaling).
         * @param margin The amount of pixels that should be used as the resulting image margin (for each side of the image). Default value is 0 (no margin).
         * @param preserveColor A Flag which indicates if the color transforms should be captured or not. Default value is true (capture color transform).
         * @param checkBounds A Flag used to scan the clip prior the rasterization in order to get the bounds of the entire MovieClip. By default is false because it adds overhead to the process.
         * @return The dynamically generated Texture Atlas.
         */
        public function fromClassVector( assets:Vector.<Class>, scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true, checkBounds:Boolean=false ):TextureAtlas
        {
            var container:MovieClip = new MovieClip();
            for each (var clazz:Class in assets) 
            {
                var display:DisplayObject = new clazz();
                display.name = getQualifiedClassName( clazz );
                container.addChild( display );
            }
            return fromMovieClip( container , scaleFactor, margin, preserveColor, checkBounds );
        }
        
        /**
         * This method will take a MovieClip sprite sheet (containing other display objects) and convert it into a Texture Atlas.
         * @param swf The MovieClip sprite sheet you wish to convert into a TextureAtlas. I must contain named instances of every display object that will be rasterized and become the subtextures of your Atlas.
         * @param scaleFactor The scaling factor to apply to every object. Default value is 1 (no scaling).
         * @param margin The amount of pixels that should be used as the resulting image margin (for each side of the image). Default value is 0 (no margin).
         * @param preserveColor A Flag which indicates if the color transforms should be captured or not. Default value is true (capture color transform).
         * @param checkBounds A Flag used to scan the clip prior the rasterization in order to get the bounds of the entire MovieClip. By default is false because it adds overhead to the process.
         * @return TextureAtlas - The dynamically generated Texture Atlas.
         */
        public function fromMovieClip( swf:Sprite, scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true, checkBounds:Boolean=false ):TextureAtlas
        {
            var selected:DisplayObject;
            var selectedTotalFrames:int;
            var selectedColorTransform:ColorTransform;
            var frameBounds:Rectangle = new Rectangle(0, 0, 0, 0);
            
            var children:uint = swf.numChildren;
            
            var canvasData:BitmapData;
            
            var texture:Texture;
            var xml:XML;
            var subText:XML;
            var atlas:TextureAtlas;
            
            var itemsLen:int;
            var itm:TextureItem;
            
            var m:uint;
            
            _margin = margin;
            _preserveColor = preserveColor;
            
            _items = [];
            
            if (!_canvas)
            {
                _canvas = new Sprite();
            }
            
            if(swf is MovieClip)
            {
                MovieClip(swf).gotoAndStop(1);
            }
            
            for (var i:uint = 0; i < children; i++)
            {
                selected = swf.getChildAt(i);
                selectedColorTransform = selected.transform.colorTransform;
                
                _x = selected.x;
                _y = selected.y;
                
                if (scaleFactor != 1)
                {
                    selected.scaleX *= scaleFactor;
                    selected.scaleY *= scaleFactor;
                    
                    if (selected.filters.length > 0)
                    {
                        var filters:Array = selected.filters;
                        var filtersLen:int = selected.filters.length;
                        var filter:Object;
                        for (var j:uint = 0; j < filtersLen; j++)
                        {
                            filter = filters[j];
                            
                            if (filter.hasOwnProperty("blurX"))
                            {
                                filter.blurX *= scaleFactor;
                                filter.blurY *= scaleFactor;
                            }
                            if (filter.hasOwnProperty("distance"))
                            {
                                filter.distance *= scaleFactor;
                            }
                        }
                        selected.filters = filters;
                    }
                }
                
                if (selected is MovieClip)
                {
                    selectedTotalFrames = MovieClip(selected).totalFrames;
                    
                    if (checkBounds) 
                    {
                        MovieClip(selected).gotoAndStop(0);
                        frameBounds = getRealBounds(selected);
                        m = 1;
                        while (++m <= selectedTotalFrames)
                        {
                            MovieClip(selected).gotoAndStop(m);
                            frameBounds = frameBounds.union(getRealBounds(selected));
                        }
                    }
                }
                else 
                {
                    selectedTotalFrames = 1;
                }
                m = 0;
                while ( ++m <= selectedTotalFrames )
                {
                    if (selected is MovieClip)
                    {
                        MovieClip(selected).gotoAndStop(m);
                    }
                    drawItem(selected, selected.name + "_" + appendIntToString(m - 1, 5), selected.name, selectedColorTransform, frameBounds);
                }
            }
            
            _currentLab = "";
            
            layoutChildren();
            
            canvasData = new BitmapData(_canvas.width, _canvas.height, true, 0x000000);
            canvasData.draw(_canvas);
            
            xml = <TextureAtlas></TextureAtlas>;
            xml.@imagePath = "atlas.png";
            
            itemsLen = _items.length;
            
            for (var k:uint = 0; k < itemsLen; k++)
            {
                itm = _items[k];
                
                itm.graphic.dispose();
                
                subText = <SubTexture />;
                 
                subText.@name = itm.textureName;
                subText.@x = itm.x;
                subText.@y = itm.y;
                subText.@width = itm.width;
                subText.@height = itm.height;
                subText.@frameX = itm.frameX;
                subText.@frameY = itm.frameY;
                subText.@frameWidth = itm.frameWidth;
                subText.@frameHeight = itm.frameHeight;
                
                if (itm.frameName != "")
                {
                    subText.@frameLabel = itm.frameName ;
                }
                
                xml.appendChild(subText);
            }
            texture = Texture.fromBitmapData(canvasData , false);
            atlas = new TextureAtlas(texture, xml);
            
            _items.length = 0;
            _canvas.removeChildren();
            
            _items = null;
            xml = null;
            _canvas = null;
            _currentLab = null;
            
            return atlas;
        }
        
        /**
         * This method will register a Bitmap Font based on each char that belongs to a String.
         * @param chars The collection of chars which will become the Bitmap Font
         * @param fontFamily The name of the Font that will be converted to a Bitmap Font
         * @param fontSize The size in pixels of the font.
         * @param bold A flag indicating if the font will be rasterized as bold.
         * @param italic A flag indicating if the font will be rasterized as italic.
         * @param horizontalGap The number of pixels that each character should have as horizontal margin (negative values are allowed). Default value is 0.
         * @param fontCustomIDg - A custom font family name indicated by the user. Helpful when using differnt effects for the same font. [Optional]
         */
        public function bitmapFontFromString( chars:String, fontFamily:String, fontSize:Number = 12, bold:Boolean = false, italic:Boolean = false, horizontalGap:int=0, fontCustomID:String=""):void 
        {
            var format:TextFormat = new TextFormat(fontFamily, fontSize, 0xFFFFFF, bold, italic);
            
            var textfield:flash.text.TextField = new flash.text.TextField();
            
            textfield.autoSize = TextFieldAutoSize.LEFT;
            
            if ( isEmbedded(fontFamily) ) 
            {
                textfield.antiAliasType = AntiAliasType.ADVANCED ;
                textfield.embedFonts    = true;
            }
            
            textfield.defaultTextFormat = format;
            textfield.text = chars;
            
            if ( fontCustomID == "" ) 
            {
                fontCustomID = fontFamily;
            }
            
            bitmapFontFromTextField( textfield, horizontalGap, fontCustomID );
        }
        
        /**
         * This method will register a Bitmap Font based on each char that belongs to a regular flash TextField, rasterizing filters and color transforms as well.
         * @param textfield The textfield that will be used to rasterize every char of the text property
         * @param horizontalGap The number of pixels that each character should have as horizontal margin (negative values are allowed). Default value is 0.
         * @param fontCustomID A custom font family name indicated by the user. Helpful when using differnt effects for the same font. [Optional]
         */
        public function bitmapFontFromTextField( textfield:flash.text.TextField, horizontalGap:int=0, fontCustomID:String="" ):void 
        {
            var charCol:Vector.<String> = Vector.<String>( textfield.text.split("") );
            var format:TextFormat = textfield.defaultTextFormat;
            var fontFamily:String = format.font;
            var fontSize:Object = format.size;
            
            var canvasData:BitmapData;
            var texture:Texture;
            var xml:XML;
            
            var myChar:String;
            
            var oldAutoSize:String = textfield.autoSize;
            textfield.autoSize = TextFieldAutoSize.LEFT;
            
            _margin = 0;
            _preserveColor = true;
            
            _items = [];
            
            var item:TextureItem;
            var itemsLen:int;
            
            if (!_canvas) 
            {
                _canvas = new Sprite();
            }
            
            if ( charCol.indexOf(" ") == -1 ) 
            {
                charCol.push(" ") ;
            }
            
            for (var i:int = charCol.length - 1; i > -1; i--) 
            {
                myChar = textfield.text = charCol[i];
                drawItem( textfield, myChar.charCodeAt().toString() );
            }
            
            _currentLab = "";
            
            layoutChildren();
            
            canvasData = new BitmapData(_canvas.width, _canvas.height, true, 0x000000);
            canvasData.draw(_canvas);
            
            itemsLen = _items.length;
            
            xml = new XML(<font></font>);
            
            var infoNode:XML = new XML(<info />);
            
                infoNode.@face = (fontCustomID == "")? fontFamily : fontCustomID;
                infoNode.@size = fontSize;
            
            xml.appendChild(infoNode);
            
            var commonNode:XML = new XML(<common />);
                commonNode.@lineHeight = fontSize;
            
            xml.appendChild(commonNode);
            xml.appendChild(new XML(<pages><page id="0" file="texture.png" /></pages>));
            
            var charsNode:XML = new XML(<chars> </chars>);
                charsNode.@count = itemsLen;
            
            var charNode:XML;
            
            for (var k:uint = 0; k < itemsLen; k++)
            {
                item = _items[k];
                
                item.graphic.dispose();
                item.graphic = null ;
                
                charNode = new XML(<char page="0" xoffset="0" yoffset="0"/>); 
                charNode.@id = item.textureName;
                charNode.@x = item.x;
                charNode.@y = item.y;
                charNode.@width = item.width;
                charNode.@height = item.height;
                charNode.@xadvance = item.width + 2 * horizontalGap ;
                charsNode.appendChild(charNode);
            }
            
            xml.appendChild(charsNode);
            
            texture = Texture.fromBitmapData( canvasData , false );
            
            starling.text.TextField.registerBitmapFont( new BitmapFont( texture , xml ) );
            
            _items.length = 0;
            _canvas.removeChildren();
            
            textfield.autoSize = oldAutoSize;
            textfield.text = charCol.join();
            
            _items = null;
            xml = null;
            _canvas = null;
            _currentLab = null;
        }
        
        /**
         * @private
         */
        private var _bitmapData:BitmapData;
        
        /**
         * @private
         */
        private var _canvas:Sprite;
        
        /**
         * @private
         */
        private var _currentLab:String;
        
        /**
         * @private
         */
        private const DEFAULT_CANVAS_WIDTH:Number = 640;
        
        /**
         * @private
         */
        private var _items:Array;
        
        /**
         * @private
         */
        private var _margin:Number;
        
        /**
         * @private
         */
        private var _matrix:Matrix;
        
        /**
         * @private
         */
        private var _preserveColor:Boolean;
        
        /**
         * @private
         */
        private var _x:Number;
        
        /**
         * @private
         */
        private var _y:Number;
        
        /**
         * @private
         */
        private function appendIntToString(num:int, numOfPlaces:int):String
        {
            var numString:String = num.toString();
            var outString:String = "";
            for ( var i:int ; i < numOfPlaces - numString.length ; i++ )
            {
                outString += "0";
            }
            return outString + numString ;
        }
        
        /**
         * @private
         */
        private function layoutChildren():void
        {
            var xPos:Number = 0;
            var yPos:Number = 0;
            var maxY:Number = 0;
            var len:int = _items.length;
            
            var itm:TextureItem;
            
            for (var i:uint = 0; i < len; i++)
            {
                itm = _items[i];
                if ((xPos + itm.width) > DEFAULT_CANVAS_WIDTH)
                {
                    xPos = 0;
                    yPos += maxY;
                    maxY = 0;
                }
                if (itm.height + 1 > maxY)
                {
                    maxY = itm.height + 1;
                }
                itm.x = xPos;
                itm.y = yPos;
                xPos += itm.width + 1;
            }
        }
        
        /**
         * This will actually rasterize the display object passed as a parameter.
         * @private
         */
        private function drawItem( display:DisplayObject, name:String = "", baseName:String = "", clipColorTransform:ColorTransform = null, frameBounds:Rectangle = null ):TextureItem
        {
            var real:Rectangle = getRealBounds( display );
            
            _bitmapData = new BitmapData( real.width, real.height, true, 0 );
            
            _matrix = display.transform.matrix;
            _matrix.translate(-real.x + _margin, -real.y + _margin);
            
            _bitmapData.draw(display, _matrix, _preserveColor ? clipColorTransform : null);
            
            var label:String = "" ;
            
            if (display is MovieClip) 
            {
                if ( display["currentLabel"] != _currentLab && display["currentLabel"] != null )
                {
                    _currentLab = display["currentLabel"];
                    label = _currentLab;
                }
            }
            
            if (frameBounds) 
            {
                real.x      = frameBounds.x - real.x;
                real.y      = frameBounds.y - real.y;
                real.width  = frameBounds.width;
                real.height = frameBounds.height;
            }
            
            var item:TextureItem = new TextureItem( _bitmapData , name, label, real.x, real.y, real.width, real.height ) ;
            
            _items.push(item);
            _canvas.addChild(item);
            
            _bitmapData = null;
            
            return item;
        }
    }
}